/////////////////////////////////////////////////////////////////////////////
// This file is part of the "Java-DAP" project, a Java implementation
// of the OPeNDAP Data Access Protocol.
//
// Copyright (c) 2007 OPeNDAP, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
/////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections;
using System.Text;
using NDODS.DAP.BaseTypes;
using NDODS.DAP.Exceptions;

namespace NDODS.DAP.Utils
{
    /// <summary> The Util class holds static methods used by this package.
    /// 
    /// </summary>
    /// <author>  jehamby
    /// </author>
    /// <version>  $Revision: 15901 $
    /// </version>
    internal class Utilities
    {
        /// <summary> Compares elements in a <code>Vector</code> of <code>BaseType</code>s and
        /// throw a <code>BadSemanticsException</code> if there are any
        /// duplicate elements.
        /// 
        /// </summary>
        /// <param name="v">       The <code>Vector</code> to check
        /// </param>
        /// <param name="varName"> the name of the variable which called us
        /// </param>
        /// <param name="typeName">the type name of the variable which called us
        /// </param>
        /// <throws>  BadSemanticsException     if there are duplicate elements </throws>
        /// <throws>  IndexOutOfBoundsException if size doesn't match the number </throws>
        /// <summary>                                   of elements in the <code>Enumeration</code>
        /// </summary>
        internal static void UniqueNames(ArrayList v, String varName, String typeName)
        {
            String[] names = SortedNames(v);
            // DEBUG: Print out names
            //for(int i=0; i<names.length; i++) {
            //  System.err.println("names[" + i + "] = " + names[i]);
            //}
            // look for any instance of consecutive names that are ==
            for (int i = 1; i < names.Length; i++)
            {
                if (names[i - 1].Equals(names[i]))
                {
                    throw new BadSemanticsException("The variable `" + names[i] + "' is used more than once in " +
                                                    typeName + " `" + varName + "'");
                }
            }
        }

        /// <summary> Takes a <code>Vector</code> of <code>BaseType</code>s, retrieves their
        /// names into an array of <code>String</code>s, and performs a Quick Sort
        /// on that array.
        /// 
        /// </summary>
        /// <param name="v">The Vector to check
        /// </param>
        /// <returns> a sorted array of <code>String</code>
        /// </returns>
        /// <throws>  BadSemanticsException if there is an element with no name </throws>
        internal static String[] SortedNames(ArrayList v)
        {
            String[] names = new String[v.Count];
            int count = 0;
            //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
            for (IEnumerator e = v.GetEnumerator(); e.MoveNext();)
            {
                //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                BaseType bt = (BaseType) e.Current;
                String tempName = bt.Name;
                if (tempName == null)
                    throw new BadSemanticsException(bt.GetType().FullName + " variable with no name");
                names[count++] = tempName;
            }
            // DEBUG: Print out names
            //for(int i=0; i<names.length; i++) {
            //  System.err.println("names[" + i + "] = " + names[i]);
            //}
            // assert that size is correct
            if (count != names.Length)
                throw new IndexOutOfRangeException("Vector size changed unexpectedly");
            QuickSort(names, 0, names.Length - 1);
            return names;
        }

        /// <summary> Internal recursive method to perform Quick Sort on name array.
        /// 
        /// </summary>
        /// <param name="a">  an array of <code>String</code>.
        /// </param>
        /// <param name="lo0">the low index to sort.
        /// </param>
        /// <param name="hi0">the high index to sort.
        /// </param>
        private static void QuickSort(String[] a, int lo0, int hi0)
        {
            int lo = lo0;
            int hi = hi0;

            if (hi0 > lo0)
            {
                // Arbitrarily establishing partition element as the array midpoint */
                String mid;
                mid = a[(lo0 + hi0)/2];

                // loop through the array until indices cross
                while (lo <= hi)
                {
                    // find the first element that is >= the partition element
                    // starting from the left index.
                    while ((lo < hi0) && (String.CompareOrdinal(a[lo], mid) < 0))
                        ++lo;

                    // find an element that is <= the partition element
                    // starting from the right index.
                    while ((hi > lo0) && (String.CompareOrdinal(a[hi], mid) > 0))
                        --hi;

                    // if the indexes have not crossed, Swap
                    if (lo <= hi)
                    {
                        Swap(a, lo, hi);
                        ++lo;
                        --hi;
                    }
                }
                // If the right index has not reached the left side of array,
                // sort the left partition.
                if (lo0 < hi)
                    QuickSort(a, lo0, hi);

                // If the left index has not reached the right side of array,
                // sort the right partition.
                if (lo < hi0)
                    QuickSort(a, lo, hi0);
            }
        }

        /// <summary> Private method to Swap two elements in the array
        /// 
        /// </summary>
        /// <param name="a">an array of <code>String</code>.
        /// </param>
        /// <param name="i">the index of the first element.
        /// </param>
        /// <param name="j">the index of the second element.
        /// </param>
        private static void Swap(String[] a, int i, int j)
        {
            String T;
            T = a[i];
            a[i] = a[j];
            a[j] = T;
        }

        /// <summary> This function escapes non-printable characters and quotes.  This is used
        /// to make <code>PrintValue</code> output <code>DString</code> data in the
        /// same way as the C++ version.  Since Java supports Unicode, this will
        /// need to be altered if it's desired to Print <code>DString</code> as
        /// UTF-8 or some other character encoding.
        /// 
        /// </summary>
        /// <param name="s">the input <code>String</code>.
        /// </param>
        /// <returns> the escaped <code>String</code>.
        /// </returns>
        internal static String EscAttr(String s)
        {
            StringBuilder buf = new StringBuilder(s.Length);
            for (int i = 0; i < s.Length; i++)
            {
                char c = s[i];
                if (c == ' ' || (c >= '!' && c <= '~'))
                {
                    // printable ASCII character
                    buf.Append(c);
                }
                else
                {
                    // non-printable ASCII character: Print as unsigned octal integer
                    // padded with leading zeros
                    buf.Append('\\');
                    String numVal = Convert.ToString(c & 0xFF, 8);
                    for (int pad = 0; pad < (3 - numVal.Length); pad++)
                        buf.Append('0');
                    buf.Append(numVal);
                }
            }
            return buf.ToString();
        }
    }
}